﻿namespace MicroCloud.Utils
{
    /// <summary>
    /// AES加解密算法帮助类
    /// </summary>
    public class AesHelper
    {
        #region "常量"
        #region "加密密钥"
        /// <summary>
        /// 加密密钥
        /// </summary>
        public const string Key = "A4qkLu6IJEd{0[~@3Zbaceg;($rlCK<vHFBx1W?)m/!VDTf*stU,]&#n`-X^oYSh";
        #endregion
        #region "向量密钥"
        /// <summary>
        /// 向量密钥
        /// </summary>
        public const string Iv = "L:4$}v(|np~O>qRM=Tzy*P5liY<c!d?Ag,ejVX'0{E_^Q&.I1xu2WF`B]@or[Kab";
        #endregion

        #endregion

        #region "私有字段"
        #region "表示所有对称算法的实现都必须从中继承的抽象基类"
        /// <summary>
        /// 表示所有对称算法的实现都必须从中继承的抽象基类
        /// </summary>
        private static readonly SymmetricAlgorithm _mobjCryptoService = Aes.Create();
        #endregion

        #endregion

        #region "方法"
        #region "加密字符串"
        /// <summary>
        /// 加密字符串
        /// </summary>
        /// <param name="encryptVal">待加密的字符串</param>
        /// <param name="key">加密密钥</param>
        /// <param name="iv">向量密钥</param>
        /// <returns>加密后的字符串</returns>
        public static string Encrypt(string encryptVal, string key = Key, string iv = Iv)
        {
            encryptVal = string.IsNullOrEmpty(encryptVal) ? "" : encryptVal;

            byte[] bytIn = Encoding.UTF8.GetBytes(encryptVal);
            using var ms = new MemoryStream();
            _mobjCryptoService.Key = GetLegalKey(key);
            _mobjCryptoService.IV = GetLegalIV(iv);
            ICryptoTransform encrypto = _mobjCryptoService.CreateEncryptor();
            using var cs = new CryptoStream(ms, encrypto, CryptoStreamMode.Write);
            cs.Write(bytIn, 0, bytIn.Length);
            cs.FlushFinalBlock();
            //ms.Close();
            byte[] bytOut = ms.ToArray();
            return Convert.ToBase64String(bytOut);
        }
        #endregion
        #region "解密字符串"
        /// <summary>
        /// 解密字符串
        /// </summary>
        /// <param name="decryptVal">待解密的字符串</param>
        /// <param name="key">加密密钥</param>
        /// <param name="iv">向量密钥</param>
        /// <returns>解密后的字符串</returns>
        public static string Decrypt(string decryptVal, string key = Key, string iv = Iv)
        {
            decryptVal = string.IsNullOrEmpty(decryptVal) ? "" : decryptVal;

            byte[] bytIn = Convert.FromBase64String(decryptVal);
            using var ms = new MemoryStream(bytIn, 0, bytIn.Length);
            _mobjCryptoService.Key = GetLegalKey(key);
            _mobjCryptoService.IV = GetLegalIV(iv);
            ICryptoTransform encrypto = _mobjCryptoService.CreateDecryptor();
            using var cs = new CryptoStream(ms, encrypto, CryptoStreamMode.Read);
            using var sr = new StreamReader(cs);
            return sr.ReadToEnd();
        }
        #endregion

        #region "加密字节数组"
        /// <summary>
        /// 加密字节数组
        /// </summary>
        /// <param name="decodeBytes">待加密的字节数组</param>
        /// <param name="key">加密密钥</param>
        /// <param name="needIV">是否需要向量</param>
        /// <returns>加密后的字节数组</returns>
        public static byte[] Encrypt(byte[] decodeBytes, string key, bool needIV = false)
        {
            decodeBytes.CheckNotNull(nameof(decodeBytes));
            using Aes aes = Aes.Create();
            Check.NotNull(aes, nameof(aes));
            //if (aes == null)
            //{
            //    throw new MicroCloudException("AES加密时获取加密实例失败");
            //}
            aes.Key = CheckKey(key);
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.ECB;
            byte[] ivBytes = Array.Empty<byte>();
            if (needIV)
            {
                aes.Mode = CipherMode.CBC;
                aes.GenerateIV();
                ivBytes = aes.IV;
            }
            using ICryptoTransform encryptor = aes.CreateEncryptor();
            byte[] encodeBytes = encryptor.TransformFinalBlock(decodeBytes, 0, decodeBytes.Length);
            aes.Clear();
            return needIV ? ivBytes.Concat(encodeBytes).ToArray() : encodeBytes;
        }
        #endregion
        #region "解密字节数组"
        /// <summary>
        /// 解密字节数组
        /// </summary>
        /// <param name="encodeBytes">待解密的字节数组</param>
        /// <param name="key">加密密钥</param>
        /// <param name="needIV">是否需要向量</param>
        /// <returns>解密后的字节数组</returns>
        public static byte[] Decrypt(byte[] encodeBytes, string key, bool needIV = false)
        {
            encodeBytes.CheckNotNull(nameof(encodeBytes));
            using Aes aes = Aes.Create();
            Check.NotNull(aes, nameof(aes));
            //if (aes == null)
            //{
            //    throw new MicroCloudException("AES加密时获取加密实例失败");
            //}
            aes.Key = CheckKey(key);
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.ECB;
            if (needIV)
            {
                aes.Mode = CipherMode.CBC;
                const int ivLength = 16;
                byte[] ivBytes = new byte[ivLength], newEncodeBytes = new byte[encodeBytes.Length - ivLength];
                Array.Copy(encodeBytes, 0, ivBytes, 0, ivLength);
                aes.IV = ivBytes;
                Array.Copy(encodeBytes, ivLength, newEncodeBytes, 0, newEncodeBytes.Length);
                encodeBytes = newEncodeBytes;
            }
            using ICryptoTransform decryptor = aes.CreateDecryptor();
            byte[] decodeBytes = decryptor.TransformFinalBlock(encodeBytes, 0, encodeBytes.Length);
            aes.Clear();
            return decodeBytes;
        }
        #endregion
        #region "加密字符串，输出为Base64字符串"
        /// <summary>
        /// 加密字符串，输出为Base64字符串
        /// </summary>
        /// <param name="source">待加密的字符串</param>
        /// <param name="key">加密密钥</param>
        /// <param name="needIV">是否需要向量</param>
        /// <returns>加密后的字符串</returns>
        public static string Encrypt(string source, string key, bool needIV = false)
        {
            source.CheckNotNull(nameof(source));

            byte[] decodeBytes = source.ToBytes();
            byte[] encodeBytes = Encrypt(decodeBytes, key, needIV);
            return Convert.ToBase64String(encodeBytes, 0, encodeBytes.Length);
        }
        #endregion
        #region "解密字符串，输入为Base64字符串"
        /// <summary>
        /// 解密字符串，输入为Base64字符串
        /// </summary>
        /// <param name="source">待解密的字符串</param>
        /// <param name="key">加密密钥</param>
        /// <param name="needIV">是否需要向量</param>
        /// <returns>解密后的字符串</returns>
        public static string Decrypt(string source, string key, bool needIV = false)
        {
            source.CheckNotNull(nameof(source));

            byte[] encodeBytes = Convert.FromBase64String(source);
            byte[] decodeBytes = Decrypt(encodeBytes, key, needIV);
            return decodeBytes.ToStr();
        }
        #endregion
        #region "加密文件"
        /// <summary>
        /// 加密文件
        /// </summary>
        /// <param name="sourceFile">源文件</param>
        /// <param name="targetFile">加密后的文件</param>
        /// <param name="key">加密密钥</param>
        /// <param name="needIV">是否需要向量</param>
        public static void EncryptFile(string sourceFile, string targetFile, string key, bool needIV = false)
        {
            sourceFile.CheckFileExists(nameof(sourceFile));
            targetFile.CheckNotNullOrEmpty(nameof(targetFile));

            using FileStream ifs = new(sourceFile, FileMode.Open, FileAccess.Read),
                    ofs = new(targetFile, FileMode.Create, FileAccess.Write);
            long length = ifs.Length;
            byte[] decodeBytes = new byte[length];
            ifs.Read(decodeBytes, 0, decodeBytes.Length);
            byte[] encodeBytes = Encrypt(decodeBytes, key, needIV);
            ofs.Write(encodeBytes, 0, encodeBytes.Length);
        }
        #endregion
        #region "解密文件"
        /// <summary>
        /// 解密文件
        /// </summary>
        /// <param name="sourceFile">源文件</param>
        /// <param name="targetFile">解密后的文件</param>
        /// <param name="key">加密密钥</param>
        /// <param name="needIV">是否需要向量</param>
        public static void DecryptFile(string sourceFile, string targetFile, string key, bool needIV = false)
        {
            sourceFile.CheckFileExists(nameof(sourceFile));
            targetFile.CheckNotNullOrEmpty(nameof(targetFile));

            using FileStream ifs = new(sourceFile, FileMode.Open, FileAccess.Read),
                    ofs = new(targetFile, FileMode.Create, FileAccess.Write);
            long length = ifs.Length;
            byte[] encodeBytes = new byte[length];
            ifs.Read(encodeBytes, 0, encodeBytes.Length);
            byte[] decodeBytes = Decrypt(encodeBytes, key, needIV);
            ofs.Write(decodeBytes, 0, decodeBytes.Length);
        }
        #endregion
        #region "获取随机密钥"
        /// <summary>
        /// 获取随机密钥
        /// </summary>
        /// <returns>随机密钥</returns>
        public static string GetRandomKey()
        {
            using Aes aes = Aes.Create();
            aes.GenerateKey();
            //Console.WriteLine(aes.Key.Length);
            return Convert.ToBase64String(aes.Key);
        }
        #endregion

        #endregion

        #region "私有方法"
        #region "获取合法的键值"
        /// <summary>
        /// 获取合法的键值
        /// </summary>
        /// <param name="key">加密密钥</param>
        /// <returns></returns>
        private static byte[] GetLegalKey(string key = Key)
        {
            string tempKey = key;
            _mobjCryptoService.GenerateKey();
            byte[] bytTemp = _mobjCryptoService.Key;
            int KeyLength = bytTemp.Length;
            if (tempKey.Length > KeyLength)
            {
                tempKey = tempKey[..KeyLength];
            }
            else if (tempKey.Length < KeyLength)
            {
                tempKey = tempKey.PadRight(KeyLength, ' ');
            }
            return Encoding.ASCII.GetBytes(tempKey);
        }
        #endregion
        #region "获得初始向量IV"
        /// <summary>
        /// 获得初始向量IV
        /// </summary>    
        /// <param name="iv">向量密钥</param>
        /// <returns>初始向量IV</returns>    
        private static byte[] GetLegalIV(string iv = Iv)
        {
            string tempIV = iv;     //key 随便写。。  
            _mobjCryptoService.GenerateIV();
            byte[] bytTemp = _mobjCryptoService.IV;
            int IVLength = bytTemp.Length;
            if (tempIV.Length > IVLength)
            {
                tempIV = tempIV[..IVLength];
            }
            else if (tempIV.Length < IVLength)
            {
                tempIV = tempIV.PadRight(IVLength, ' ');
            }
            return Encoding.ASCII.GetBytes(tempIV);
        }
        #endregion

        #region "获取密钥的字节数组，AES加密密钥必须是32位，不是32位自动补全或者截断"
        /// <summary>
        /// 获取密钥的字节数组，AES加密密钥必须是32位，不是32位自动补全或者截断
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        private static byte[] CheckKey(string key)
        {
            key.CheckNotNull(nameof(key));
            byte[] bytes, keyBytes = new byte[32];
            try
            {
                bytes = Convert.FromBase64String(key);
            }
            catch (FormatException)
            {
                bytes = key.ToBytes();
            }
            if (bytes.Length < 32)
            {
                Array.Copy(bytes, 0, keyBytes, 0, bytes.Length);
            }
            else if (bytes.Length > 32)
            {
                Array.Copy(bytes, 0, keyBytes, 0, 32);
            }
            else
            {
                keyBytes = bytes;
            }
            return keyBytes;
        }
        #endregion

        #endregion

    }

    /// <summary>
    /// AES算法类
    /// </summary>
    public class AesAlgorithm
    {
        /// <summary>
        /// 是否需要向量
        /// </summary>
        private readonly bool _needIV;

        #region "属性"
        #region "获取 加密密钥"
        /// <summary>
        /// 获取 加密密钥
        /// </summary>
        public string Key { get; }
        #endregion

        #endregion

        #region "构造函数"
        #region "初始化一个AES算法类的新实例"
        /// <summary>
        /// 初始化一个AES算法类 <see cref="AesAlgorithm"/> 的新实例
        /// </summary>
        /// <param name="needIV">是否需要向量</param>
        public AesAlgorithm(bool needIV = false)
            : this(AesHelper.GetRandomKey(), needIV)
        { }
        #endregion
        #region "初始化一个AES算法类的新实例"
        /// <summary>
        /// 初始化一个AES算法类 <see cref="AesAlgorithm"/> 的新实例
        /// </summary>
        /// <param name="key">加密密钥</param>
        /// <param name="needIV">是否需要向量</param>
        public AesAlgorithm(string key, bool needIV = false)
        {
            Key = key;
            _needIV = needIV;
        }
        #endregion

        #endregion

        #region "方法"
        #region "加密字节数组"
        /// <summary>
        /// 加密字节数组
        /// </summary>
        /// <param name="decodeBytes">需要加密的字节数组</param>
        /// <returns>加密后的字节数组</returns>
        public byte[] Encrypt(byte[] decodeBytes)
        {
            return AesHelper.Encrypt(decodeBytes, Key, _needIV);
        }
        #endregion
        #region "解密字节数组"
        /// <summary>
        /// 解密字节数组
        /// </summary>
        /// <param name="encodeBytes">需要解密的字节数组</param>
        /// <returns>解密后的字节数组</returns>
        public byte[] Decrypt(byte[] encodeBytes)
        {
            return AesHelper.Decrypt(encodeBytes, Key, _needIV);
        }
        #endregion

        #region "加密字符串，输出为Base64编码的字符串"
        /// <summary>
        /// 加密字符串，输出为Base64编码的字符串
        /// </summary>
        /// <param name="source">需要加密的字符串</param>
        /// <returns>加密后的Base64编码的字符串</returns>
        public string Encrypt(string source)
        {
            return AesHelper.Encrypt(source, Key, _needIV);
        }
        #endregion
        #region "解密字符串，输入为Base64编码的字符串"
        /// <summary>
        /// 解密字符串，输入为Base64编码的字符串
        /// </summary>
        /// <param name="source">加密后的Base64编码的字符串</param>
        /// <returns>解密后字符串</returns>
        public string Decrypt(string source)
        {
            return AesHelper.Decrypt(source, Key, _needIV);
        }
        #endregion

        #region "加密文件"
        /// <summary>
        /// 加密文件
        /// </summary>
        /// <param name="sourceFile">源文件</param>
        /// <param name="targetFile">加密后的文件</param>
        public void EncryptFile(string sourceFile, string targetFile)
        {
            AesHelper.EncryptFile(sourceFile, targetFile, Key, _needIV);
        }
        #endregion
        #region "解密文件"
        /// <summary>
        /// 解密文件
        /// </summary>
        /// <param name="sourceFile">源文件</param>
        /// <param name="targetFile">解密后的文件</param>
        public void DecryptFile(string sourceFile, string targetFile)
        {
            AesHelper.DecryptFile(sourceFile, targetFile, Key, _needIV);
        }
        #endregion

        #endregion

    }

}
